package com.hivekion.common.aspects;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.PropertyFilter;
import com.hivekion.common.IP.IPUtils;
import com.hivekion.common.annotation.AutoLog;
import com.hivekion.system.domain.SysOperationLog;
import com.hivekion.system.mapper.SysOperationLogMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

@Component
@Aspect
public class ControllerLogAspect {
      public static final Logger logger = LoggerFactory.getLogger(ControllerLogAspect.class);

      @Autowired
	  SysOperationLogMapper mapper;

      @Pointcut("@annotation(com.hivekion.common.annotation.AutoLog)")
      public void operationLog() {
      }

      @Pointcut("execution(* com.hivekion.*.controller.*.*(..))")
      public void controllerThrowLog() {
      }

      @Around("operationLog()")
      public Object handleControllerMethod(ProceedingJoinPoint point) throws Throwable {
	    Object result = null; // 执行时长
	    long beginTime = System.currentTimeMillis(); // 开始时间
	    result = point.proceed();
	    try {
		  long time = System.currentTimeMillis() - beginTime; // 计算运行时间
		  addOperationLog(point, result, time, 0);
	    } catch (Exception ex) {
		  logger.error("LogAspect 操作失败: " + ex.getMessage());
	    }
	    return result;
      }

      @AfterThrowing(value = "controllerThrowLog()", throwing = "e")
      public void handleControllerThrowMethod(JoinPoint point, Throwable e) {
	    addOperationLog(point, e, 0, 1);
      }

      private void addOperationLog(JoinPoint point, Object res, long time, int status) {
	    HttpServletRequest request =
		    ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
			    .getRequest();
	    MethodSignature signature = (MethodSignature) point.getSignature();
	    SysOperationLog operationLog = new SysOperationLog();
	    operationLog.setRunTime(time); // 记录运行时间
	    String returnV = JSON.toJSONString(res);
	    int length = returnV.length();
	    operationLog.setReturnValue(returnV); // 记录返回结果
	    // operationLog.setArgs(JSON.toJSONString(point.getArgs()));
	    //            // 请求参数
	    operationLog.setMethod(signature.getDeclaringTypeName() + "." + signature.getName()); //
	    operationLog.setRequestParams(getReqestParams(request, point));
	    operationLog.setIp(IPUtils.getIpAddr(request)); // 访问IP地址
	    operationLog.setStatus(status);
	    AutoLog annotation = signature.getMethod().getAnnotation(AutoLog.class); // 注解
	    if (annotation != null) {
		  operationLog.setLevel(annotation.level());
		  // operationLog.setDescribe(getDetail(((MethodSignature)
		  // point.getSignature()).getParameterNames(), point.getArgs(), annotation));
		  operationLog.setOperationType(annotation.operationType().getValue()); // 操作类型
		  operationLog.setOperationUnit(annotation.operationUnit().getValue()); // 被操作对象
	    }
	    // TODO 这里保存日志
	    System.out.println("记录日志:" + operationLog.toString());
	    mapper.insert(operationLog);
      }

      private String getReqestParams(HttpServletRequest request, JoinPoint joinPoint) {
	    String httpMethod = request.getMethod();
	    String params = "";
	    if ("POST".equals(httpMethod) || "PUT".equals(httpMethod) || "PATCH".equals(httpMethod)) {
		  Object[] paramsArray = joinPoint.getArgs();
		  // java.lang.IllegalStateException: It is illegal to call this method if the current
		  // request
		  // is not in asynchronous mode (i.e. isAsyncStarted() returns false)
		  //  https://my.oschina.net/mengzhang6/blog/2395893
		  Object[] arguments = new Object[paramsArray.length];
		  for (int i = 0; i < paramsArray.length; i++) {
			if (paramsArray[i] instanceof ServletRequest
				|| paramsArray[i] instanceof ServletResponse
				|| paramsArray[i] instanceof MultipartFile
				|| paramsArray[i] instanceof MultipartFile[]) {
			      // ServletRequest不能序列化，从入参里排除，否则报异常：java.lang.IllegalStateException: It is
			      // illegal to call
			      // this method if the current request is not in asynchronous mode (i.e.
			      // isAsyncStarted()
			      // returns false)
			      // ServletResponse不能序列化 从入参里排除，否则报异常：java.lang.IllegalStateException:
			      // getOutputStream()
			      // has already been called for this response
			      continue;
			}
			arguments[i] = paramsArray[i];
		  }
		  // update-begin-author:taoyan date:20200724 for:日志数据太长的直接过滤掉
		  PropertyFilter profilter =
			  new PropertyFilter() {
				@Override
				public boolean apply(Object o, String name, Object value) {
				      if (value != null && value.toString().length() > 500) {
					    return false;
				      }
				      return true;
				}
			  };
		  params = JSONObject.toJSONString(arguments, profilter);
		  // update-end-author:taoyan date:20200724 for:日志数据太长的直接过滤掉
	    } else {
		  MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		  Method method = signature.getMethod();
		  // 请求的方法参数值
		  Object[] args = joinPoint.getArgs();
		  // 请求的方法参数名称
		  LocalVariableTableParameterNameDiscoverer u =
			  new LocalVariableTableParameterNameDiscoverer();
		  String[] paramNames = u.getParameterNames(method);
		  if (args != null && paramNames != null) {
			for (int i = 0; i < args.length; i++) {
			      params += "  " + paramNames[i] + ": " + args[i];
			}
		  }
	    }
	    return params;
      }
}
